/**
 * @license
 * Copyright 2019 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import * as sinon from 'sinon';
import '../../../test/common-test-setup';
import './gr-apply-fix-dialog';
import {
  NavigationService,
  navigationToken,
} from '../../core/gr-navigation/gr-navigation';
import {queryAndAssert, stubRestApi} from '../../../test/test-utils';
import {GrApplyFixDialog} from './gr-apply-fix-dialog';
import {PatchSetNum, PatchSetNumber} from '../../../types/common';
import {
  createFixSuggestionInfo,
  createParsedChange,
  createRange,
  createRevisions,
  getCurrentRevision,
} from '../../../test/test-data-generators';
import {createDefaultDiffPrefs} from '../../../constants/constants';
import {OpenFixPreviewEventDetail} from '../../../types/events';
import {GrButton} from '../../shared/gr-button/gr-button';
import {assert, fixture, html} from '@open-wc/testing';
import {SinonStubbedMember} from 'sinon';
import {testResolver} from '../../../test/common-test-setup';
import {PROVIDED_FIX_ID} from '../../../utils/comment-util';

suite('gr-apply-fix-dialog tests', () => {
  let element: GrApplyFixDialog;
  let setUrlStub: SinonStubbedMember<NavigationService['setUrl']>;

  const TWO_FIXES: OpenFixPreviewEventDetail = {
    patchNum: 2 as PatchSetNum,
    fixSuggestions: [
      createFixSuggestionInfo('fix_1'),
      createFixSuggestionInfo('fix_2'),
    ],
    onCloseFixPreviewCallbacks: [],
  };

  const ONE_FIX: OpenFixPreviewEventDetail = {
    patchNum: 2 as PatchSetNum,
    fixSuggestions: [createFixSuggestionInfo('fix_1')],
    onCloseFixPreviewCallbacks: [],
  };

  function getConfirmButton(): GrButton {
    return queryAndAssert(
      queryAndAssert(element, '#applyFixDialog'),
      '#confirm'
    );
  }

  async function open(detail: OpenFixPreviewEventDetail) {
    element.open(
      new CustomEvent<OpenFixPreviewEventDetail>('open-fix-preview', {
        detail,
      })
    );
    await element.updateComplete;
  }

  setup(async () => {
    setUrlStub = sinon.stub(testResolver(navigationToken), 'setUrl');
    element = await fixture<GrApplyFixDialog>(
      html`<gr-apply-fix-dialog></gr-apply-fix-dialog>`
    );
    const change = {
      ...createParsedChange(),
      revisions: createRevisions(2),
      current_revision: getCurrentRevision(1),
    };
    element.changeNum = change._number;
    element.patchNum = change.revisions[change.current_revision]._number;
    element.latestPatchNum = change.revisions[change.current_revision]
      ._number as PatchSetNumber;
    element.change = change;
    element.diffPrefs = {
      ...createDefaultDiffPrefs(),
      font_size: 12,
      line_length: 100,
      tab_size: 4,
    };
    await element.updateComplete;
  });

  suite('dialog open', () => {
    setup(() => {
      sinon.stub(element.applyFixModal!, 'showModal');
    });

    test('dialog opens fetch and sets previews', async () => {
      await open(TWO_FIXES);
      assert.equal(element.currentFix!.fix_id, 'fix_1');
      assert.equal(element.currentPreviews.length, 0);
      const button = getConfirmButton();
      assert.isFalse(button.hasAttribute('disabled'));
      assert.equal(button.getAttribute('title'), '');
    });

    test('tooltip is hidden if apply fix is loading', async () => {
      element.isApplyFixLoading = true;
      await open(TWO_FIXES);
      const button = getConfirmButton();
      assert.isTrue(button.hasAttribute('disabled'));
      assert.equal(button.getAttribute('title'), 'Fix is still loading ...');
    });
  });

  test('renders', async () => {
    await open(TWO_FIXES);
    assert.shadowDom.equal(
      element,
      /* HTML */ `
        <dialog id="applyFixModal" tabindex="-1" open="">
          <gr-dialog id="applyFixDialog" role="dialog">
            <div slot="header">Fix fix_1</div>
            <div slot="main"></div>
            <div class="fix-picker" slot="footer">
              <span>Suggested fix 1 of 2</span>
              <gr-button
                aria-disabled="true"
                disabled=""
                id="prevFix"
                role="button"
                tabindex="-1"
              >
                <gr-icon icon="chevron_left"></gr-icon>
              </gr-button>
              <gr-button
                aria-disabled="false"
                id="nextFix"
                role="button"
                tabindex="0"
              >
                <gr-icon icon="chevron_right"></gr-icon>
              </gr-button>
            </div>
          </gr-dialog>
        </dialog>
      `,
      {ignoreAttributes: ['style']}
    );
  });

  test('next button state updated when suggestions changed', async () => {
    await open(ONE_FIX);
    await element.updateComplete;
    assert.notOk(element.nextFix);
    element.applyFixModal?.close();

    await open(TWO_FIXES);
    assert.ok(element.nextFix);
    assert.notOk(element.nextFix.disabled);
  });

  test('select fix forward and back of multiple suggested fixes', async () => {
    sinon.stub(element.applyFixModal!, 'showModal');

    await open(TWO_FIXES);
    element.onNextFixClick(new CustomEvent('click'));
    assert.equal(element.currentFix!.fix_id, 'fix_2');
    element.onPrevFixClick(new CustomEvent('click'));
    assert.equal(element.currentFix!.fix_id, 'fix_1');
  });

  test('onCancel fires close with correct parameters', () => {
    const closeFixPreviewEventSpy = sinon.spy();
    element.onCloseFixPreviewCallbacks.push(closeFixPreviewEventSpy);
    element.onCancel(new CustomEvent('cancel'));
    sinon.assert.calledOnceWithExactly(closeFixPreviewEventSpy, false);
  });

  test('applies second fix with PROVIDED_FIX_ID', async () => {
    const applyFixSuggestionStub = stubRestApi('applyFixSuggestion').returns(
      Promise.resolve(new Response(null, {status: 200}))
    );

    const fixes: OpenFixPreviewEventDetail = {
      patchNum: 2 as PatchSetNum,
      fixSuggestions: [
        {
          ...createFixSuggestionInfo('fix_1'),
          fix_id: PROVIDED_FIX_ID,
          replacements: [
            {
              path: 'file1.txt',
              replacement: 'new content',
              range: createRange(),
            },
          ],
        },
        {
          ...createFixSuggestionInfo('fix_2'),
          fix_id: PROVIDED_FIX_ID,
          replacements: [
            {
              path: 'file2.txt',
              replacement: 'other content',
              range: createRange(),
            },
          ],
        },
      ],
      onCloseFixPreviewCallbacks: [],
    };

    await open(fixes);
    element.onNextFixClick(new CustomEvent('click'));
    await element.updateComplete;

    await element.handleApplyFix(new CustomEvent('confirm'));

    sinon.assert.calledOnceWithExactly(
      applyFixSuggestionStub,
      element.change!._number,
      2 as PatchSetNum,
      [{path: 'file2.txt', replacement: 'other content', range: createRange()}],
      element.latestPatchNum
    );
    assert.isTrue(setUrlStub.called);
    assert.equal(
      setUrlStub.lastCall.firstArg,
      '/c/test-project/+/42/2..edit?forceReload=true'
    );
  });
});
